#include "DXUT.h"

#include "Landmap.h"
#include "../MathUtil.h"

double Landmap::calcDensity(int x, int y, int z, Biomemap * pBiome) {
	double height = calcBaseTerrain(x, z);
	double ocean = calcOceanTerrain(x, z);
	double river = calcRiverTerrain(x, z);

	float temp = clamp((pBiome->getTemperatur(x, z) + 1.0) / 2.0);//0.9f;//biomeProvider.getTemperatureAt(x, z);
	float humidity = clamp((pBiome->getHumidity(x, z) + 1.0) / 2.0) * temp;//biomeProvider.getHumidityAt(x, z) * temp;

	D3DXVECTOR2 distanceToMountainBiome = D3DXVECTOR2(temp - 0.25f, humidity - 0.35f);

	double mIntens = clamp(1.0 - D3DXVec2Length(&distanceToMountainBiome) * 3.0);
	double densityMountains = calcMountainDensity(x, y, z) * mIntens;
	double densityHills = calcHillDensity(x, y, z) * (1.0 - mIntens);

	int plateauArea = 26;
	double flatten = clamp((200.0f-y) / plateauArea);

	return -y + (16.0 + ((32.0 + ((16.0 + height * 24.0) * clamp(river + 0.25))) * clamp(ocean + 0.25)) + densityHills * 24.0 + densityMountains * 1024.0) * flatten;
}

double Landmap::calcBaseTerrain(double x, double z) {
	return clamp((pGen1.GetValue(0.004 * x, 0, 0.004 * z) + 1.0) / 2.0);
}

double Landmap::calcOceanTerrain(double x, double z) {
	return clamp(pGen2.GetValue(0.0009 * x, 0, 0.0009 * z) * 8.0);
}

double Landmap::calcRiverTerrain(double x, double z) {
	return clamp((sqrt(abs(pGen3.GetValue(0.0008 * x, 0, 0.0008 * z))) - 0.1) * 7.0);
}

double Landmap::calcMountainDensity(double x, double y, double z) {
	double x1, y1, z1;
	x1 = x * 0.002;y1 = y * 0.001; z1 = z * 0.002;

	double result = pGen4.GetValue(x1, y1, z1);
	result *= abs(result);
	return result > 0.0 ? result : 0;
}

double Landmap::calcHillDensity(double x, double y, double z) {
	double x1, y1, z1;
	x1 = x * 0.008; y1 = y * 0.006; z1 = z * 0.008;

	double result = pGen5.GetValue(x1, y1, z1) - 0.1;
	return result > 0.0 ? result : 0;
}

double Landmap::calcCaveDensity(int x, int y, int z) {
	return pGen6.GetValue(x * 0.02, y * 0.02, z * 0.02);
}

void Landmap::init(int seed, int octaves, int x, int z) {
	_x = x;
	_z = z;

	pGen0.SetSeed(seed);
	pGen1.SetSeed(seed + 1);
	pGen1.SetOctaveCount(8);
	pGen2.SetSeed(seed + 2);
	pGen2.SetOctaveCount(8);
	pGen3.SetSeed(seed + 3);
	pGen3.SetOctaveCount(8);
	pGen4.SetSeed(seed + 4);
	pGen5.SetSeed(seed + 5);
	pGen6.SetSeed(seed + 6);
	pGen6.SetOctaveCount(octaves);
}

Landmap::Landmap(int seed, int octaves, int x, int z)  {
	init(seed, octaves, x, z);
}

Landmap::Landmap(int seed, int x, int z)  {
	init(seed, 2, x, z);
}

Landmap::Landmap(int x, int z)  {
	init(0, 2, x, z);
}

Landmap::~Landmap() {

}

inline int getDensityMapPos(int x, int y, int z) {
	return x + (y * 17) + (z * 4369);
}

void Landmap::triLerpDensityMap(double* densityMap) {
	for (int x = 0; x < 16; x++) {
		for (int z = 0; z < 16; z++) {
			for (int y = 0; y < 256; y++) {
				if (!(x % SAMPLE_RATE_3D_HOR == 0 && y % SAMPLE_RATE_3D_VERT == 0 && z % SAMPLE_RATE_3D_HOR == 0)) {
					int offsetX = (x / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;
					int offsetY = (y / SAMPLE_RATE_3D_VERT) * SAMPLE_RATE_3D_VERT;
					int offsetZ = (z / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;
						densityMap[getDensityMapPos(x,y,z)] = triLerp(x, y, z, 
						densityMap[getDensityMapPos(offsetX,offsetY,offsetZ)], 
						densityMap[getDensityMapPos(offsetX,SAMPLE_RATE_3D_VERT + offsetY,offsetZ)], 
						densityMap[getDensityMapPos(offsetX,offsetY,offsetZ + SAMPLE_RATE_3D_HOR)], 
						densityMap[getDensityMapPos(offsetX,offsetY + SAMPLE_RATE_3D_VERT,offsetZ + SAMPLE_RATE_3D_HOR)], 
						densityMap[getDensityMapPos(SAMPLE_RATE_3D_HOR + offsetX,offsetY,offsetZ)], 
						densityMap[getDensityMapPos(SAMPLE_RATE_3D_HOR + offsetX,offsetY + SAMPLE_RATE_3D_VERT,offsetZ)], 
						densityMap[getDensityMapPos(SAMPLE_RATE_3D_HOR + offsetX,offsetY,offsetZ + SAMPLE_RATE_3D_HOR)], 
						densityMap[getDensityMapPos(SAMPLE_RATE_3D_HOR + offsetX,offsetY + SAMPLE_RATE_3D_VERT,offsetZ + SAMPLE_RATE_3D_HOR)], 
						offsetX, 
						SAMPLE_RATE_3D_HOR + offsetX, 
						offsetY, SAMPLE_RATE_3D_VERT + offsetY, 
						offsetZ, 
						offsetZ + SAMPLE_RATE_3D_HOR);
				}
			}
		}
	}
}

void Landmap::GenerateInnerLayer(int x, int y, int z, Block &mem,  BYTE type) {
	// TODO: GENERATE MINERALS HERE - config waiting at org\terasology\logic\manager\DefaultConfig.groovy 2012/01/22
	//c->setBlock(x, z, y % 16, STONE_BLOCK);
	mem = STONE_BLOCK;
}

void Landmap::GenerateOuterLayer(int x, int y, int z, int firstBlockHeight, Block &mem, BYTE type) {

	int depth = (firstBlockHeight - y);

	switch (type) {
	case SWAMP:
		if (depth == 0 && y >= 64) {
			// Grass on top
			//c->setBlock(x, z, y % 16, GRASS_BLOCK);
			mem = GRASS_BLOCK; 
		} else if (depth > (3 + rand() % 3)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	case FORREST:
		if (depth == 0 && y >= 64) {
			// Grass on top
			//c->setBlock(x, z, y % 16, GRASS_BLOCK);
			mem = GRASS_BLOCK;
		} else if (depth > (2 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	case JUNGLE:
		// Beach
		if (depth < 4 && y >= 60 && y <= 66) {
			//c->setBlock(x, z, y % 16, SAND_BLOCK);
			mem = SAND_BLOCK;
		} else if (depth == 0 && y >= 64) {
			// Grass on top
			//c->setBlock(x, z, y % 16, GRASS_BLOCK);
			mem = GRASS_BLOCK;
		} else if (depth > (1 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	case MOUNTAINS:
		if (depth == 0 && y >= 64 && y < 168) {
			// Grass on top
			//c->setBlock(x, z, y % 16, GRASS_BLOCK);
			mem = GRASS_BLOCK;
		} else if (depth == 0 && y >= 168) {
			// Snow top
			//c->setBlock(x, z, y % 16, SNOWYGRASS_BLOCK);
			mem = SNOWYGRASS_BLOCK;
		} else if (depth > (2 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	case TAIGA:
		if (depth == 0.0 && y >= 64) {
			// Snow on top
			//c->setBlock(x, z, y % 16, SNOWYGRASS_BLOCK);
			mem = SNOWYGRASS_BLOCK;
		} else if (depth > (2 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	case DESERT:
		if (depth > (2 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			//c->setBlock(x, z, y % 16, SAND_BLOCK);
			mem = SAND_BLOCK;
		}
		break;
	case PLAINS:
		// Beach
		if (depth < 3 && y >= 61 && y <= 66) {
			//c->setBlock(x, z, y % 16, SAND_BLOCK);
			mem = SAND_BLOCK;
		} else if (depth == 0.0 && y >= 64) {
			// Grass on top
			//c->setBlock(x, z, y % 16, GRASS_BLOCK);
			mem = GRASS_BLOCK;
		} else if (depth > (2 + rand() % 2)) {
			// Stone
			//c->setBlock(x, z, y % 16, STONE_BLOCK);
			mem = STONE_BLOCK;
		} else {
			// Dirt
			//c->setBlock(x, z, y % 16, DIRT_BLOCK);
			mem = DIRT_BLOCK;
		}
		break;
	}
}


void Landmap::generateChunk(int rx, int rz, Chunk ** pChunks, Biomemap * pBiome) {
	double* densityMap = (double*) calloc(17 * 257 * 17, sizeof( double));

	/*
	* Create the density map at a lower sample rate.
	*/
	for (int x = 0; x <= 16; x += SAMPLE_RATE_3D_HOR) {
		for (int z = 0; z <= 16; z += SAMPLE_RATE_3D_HOR) {
			for (int y = 0; y <= 256; y += SAMPLE_RATE_3D_VERT) {
				densityMap[x + (y * 17) + (z * 4369)] = calcDensity(rx + x, y, rz + z, pBiome);
			}
		}
	}

	/*
	* Trilinear interpolate the missing values.
	*/
	triLerpDensityMap(densityMap);

	/*
	* Generate the chunk from the density map.
	*/
	Chunk ** chunks = (Chunk**) malloc(sizeof(Chunk*) * 16);
	for (int i = 0; i < 16; i++) {
		chunks[i] = pChunks[(i << 8)];
	}

	for (int x = 0; x < 16; x++) {
		for (int z = 0; z < 16; z++) {
			//WorldBiomeProvider.Biome type = biomeProvider.getTemperatureAt(c.getBlockWorldPosX(x), c.getBlockWorldPosZ(z));
			BYTE type = pBiome->getBiome(rx + x, rz + z);
			int firstBlockHeight = -1;

			for (int y = 255; y >= 0; y--) {
				Block * mem = (Block*) chunks[y / 16]->chunkGetMemory();
				int i = x + (z << 4) + ((y % 16) << 8);

				if (y == 0) { // The very deepest layer of the world is an indestructible mantle
					//c->setBlock(x, z, y % 16, STONE_BLOCK);
					mem[i] = STONE_BLOCK;
					break;
				}

				if ((y <= 64) && (y > 0)) { // Ocean
					//c->setBlock(x, z, y % 16, STILLWATER_BLOCK);

					if (y == 64) {
						// Ice layer
						mem[i] = STILLWATER_BLOCK;
						mem[i].metaLo -= 1;
					} else {
						mem[i] = STILLWATER_BLOCK;
					}
				}

				double dens = densityMap[x + (y * 17) + (z * 4369)];


				if ((dens >= 0.0) && (dens < 5.0)) {
					// Some block was set...
					if (firstBlockHeight == -1)
						firstBlockHeight = y;

					if (calcCaveDensity(rx + x, y, rz + z) < (1.0f - clamp((y - 70.0f) / 100.0f, 0.0f, 0.4f))) {
						GenerateOuterLayer(x, y, z, firstBlockHeight, mem[i], type);
					} else {
						mem[i] = AIR_BLOCK;
						firstBlockHeight = -1;
					}

					continue;
				} else if ((dens >= 5.0) && (dens < 50.0)) {
					// Some block was set...
					if (firstBlockHeight == -1)
						firstBlockHeight = y;

					if (calcCaveDensity(rx + x, y, rz + z) < 0.6)
						GenerateOuterLayer(x, y, z, firstBlockHeight, mem[i], type);
					else
						mem[i] = AIR_BLOCK;

					continue;
				} else if (dens >= 50.0) {

					// Some block was set...
					if (firstBlockHeight == -1)
						firstBlockHeight = y;

					if (calcCaveDensity(rx + x, y, rz + z) < 0.6)
						GenerateInnerLayer(x, y, z, mem[i], type);
					else if (y < 10) {
						mem[i] = STILLLAVA_BLOCK;
						if (y == 9)
							mem[i].metaLo -= 1;
					} else
						mem[i] = AIR_BLOCK;

					continue;
				}
				// Nothing was set!
				firstBlockHeight = -1;
			}
		}
	}
	free( chunks );	
	free( densityMap );
}